package vip.xiaonuo.smzq.modular.lifecycle.util;

import cn.hutool.core.util.StrUtil;
import org.apache.commons.collections4.map.LinkedMap;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.WKTReader2;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import vip.xiaonuo.smzq.core.utils.BasicUtil;
import vip.xiaonuo.smzq.core.utils.GeometryUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 坐标工具
 *
 * @liuliya
 */
public class CoordinateUtil {
    // WGS84标准参考椭球中的地球长半径(单位:米)
    private static final double EARTH_RADIUS_WGS84 = 6378137.0;

    //十进制经度
    public static final String LNG_PATTERN = "^[\\-\\+]?(0(\\.\\d{1,14})?|([1-9](\\d)?)(\\.\\d{1,14})?|1[0-7]\\d{1}(\\.\\d{1,14})?|180(\\.0{1,14})?)$";
    //纬度
    public static final String LAT_PATTERN = "^[\\-\\+]?((0|([1-8]\\d?))(\\.\\d{1,14})?|90(\\.0{1,14})?)$";
    /*
     * 度分秒正则
     * 每节的数字不能以 0 开头（比如不能写 08度，而要写 8 度）；
     * 秒的数字可以是小数，小数点后最多有两位数字；
     * 分隔三个节的标志符可以是空格、中横线、逗号、分号、°′"或者度分秒；
     * 取值范围，经度为 0度0分0秒 至 180度0分0秒；纬度为 0度0分0秒 至 90度0分0秒。
     * */
    //经度
    public static final String LNG_PATTERN_D = "^((\\d|[1-9]\\d|1[0-7]\\d)[°](\\d|[0-5]\\d)[′](\\d|[0-5]\\d)(\\.\\d{1,6})?[\\″]$)|(180[°]0[′]0[\\″]$)";
    //纬度
    public static final String LAT_PATTERN_D = "^((\\d|[1-8]\\d)[°](\\d|[0-5]\\d)[′](\\d|[0-5]\\d)(\\.\\d{1,6})?[\\″]$)|(90[°]0[′]0[\\″]$)";
    /**
     * 计算两个坐标的距离(粗略计算，单位:米)
     * 计算公式参照 google map 的距离计算
     *
     * @param lat1 坐标1纬度
     * @param lng1 坐标1经度
     * @param lat2 坐标2纬度
     * @param lng2 坐标2经度
     * @return
     */
    public static double distance(double lat1, double lng1, double lat2, double lng2) {
        double radLat1 = Math.toRadians(lat1);
        double radLat2 = Math.toRadians(lat2);

        double a = radLat1 - radLat2;
        double b = Math.toRadians(lng1) - Math.toRadians(lng2);

        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
                Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        return Math.round(s * EARTH_RADIUS_WGS84);
    }
    /**
     * 度分秒转十进制度
     *
     * @param jwd
     * @return
     */
    public static String Dms2D(String jwd) {
        if (StrUtil.isNotEmpty(jwd) && (jwd.contains("°"))) {//如果不为空并且存在度单位
            //计算前进行数据处理
            jwd = jwd.replace("E", "").replace("N", "").replace(":", "").replace("：", "");
            double d = 0, m = 0, s = 0;
            d = Double.parseDouble(jwd.split("°")[0]);
            //不同单位的分，可扩展
            if (jwd.contains("′")) {//正常的′
                m = Double.parseDouble(jwd.split("°")[1].split("′")[0]);
            } else if (jwd.contains("'")) {//特殊的'
                m = Double.parseDouble(jwd.split("°")[1].split("'")[0]);
            }
            //不同单位的秒，可扩展
            if (jwd.contains("″")) {//正常的″
                //有时候没有分 如：112°10.25″
                s = jwd.contains("′") ? Double.parseDouble(jwd.split("′")[1].split("″")[0]) : Double.parseDouble(jwd.split("°")[1].split("″")[0]);
            } else if (jwd.contains("''")) {//特殊的''
                //有时候没有分 如：112°10.25''
                s = jwd.contains("'") ? Double.parseDouble(jwd.split("'")[1].split("''")[0]) : Double.parseDouble(jwd.split("°")[1].split("''")[0]);
            }
            jwd = String.valueOf(d + m / 60 + s / 60 / 60);//计算并转换为string
        }
        return jwd;
    }

    /**
     * 坐标转换
     *
     * @param y
     * @param x
     * @param dh
     * @return
     * @throws Exception
     */
    public static double[] transTo4490fromProjectionByEpsg(double x, double y, String dh) throws Exception {
        String epsgSource = "";
        switch (dh) {
            //6度分带
            case "13":
                epsgSource = "EPSG:4491";
                break;
            case "14":
                epsgSource = "EPSG:4492";
                break;
            case "15":
                epsgSource = "EPSG:4493";
                break;
            case "16":
                epsgSource = "EPSG:4494";
                break;
            case "17":
                epsgSource = "EPSG:4495";
                break;
            case "18":
                epsgSource = "EPSG:4496";
                break;
            case "19":
                epsgSource = "EPSG:4497";
                break;
            case "20":
                epsgSource = "EPSG:4498";
                break;
            case "21":
                epsgSource = "EPSG:4499";
                break;
            case "22":
                epsgSource = "EPSG:4500";
                break;
            case "23":
                epsgSource = "EPSG:4501";
                break;
            //3度分带
            case "25":
                epsgSource = "EPSG:4513";
                break;
            case "26": //
                epsgSource = "EPSG:4514";
                break;
            case "27": //
                epsgSource = "EPSG:4515";
                break;
            case "28":  //
                epsgSource = "EPSG:4516";
                break;
            case "29":  //
                epsgSource = "EPSG:4517";
                break;
            case "30":  //
                epsgSource = "EPSG:4518";
                break;
            case "31":  //
                epsgSource = "EPSG:4519";
                break;
            case "32":  //
                epsgSource = "EPSG:4520";
                break;
            case "33":  //
                epsgSource = "EPSG:4521";
                break;
            case "34":   //
                epsgSource = "EPSG:4522";
                break;
            case "35":   //
                epsgSource = "EPSG:4523";
                break;
            case "36":  //
                epsgSource = "EPSG:4524";
                break;
            case "37":  //
                epsgSource = "EPSG:4525";
                break;
            case "38":   //'EPSG:':
                epsgSource = "EPSG:4526";
                break;
            case "39"://'EPSG:':
                epsgSource = "EPSG:4527";
                break;
            case "40"://'EPSG:':
                epsgSource = "EPSG:4528";
                break;
            case "41"://'EPSG:':
                epsgSource = "EPSG:4529";
                break;
            case "42"://'EPSG:':
                epsgSource = "EPSG:4530";
                break;
            case "43"://'EPSG:':
                epsgSource = "EPSG:4531";
                break;
            case "44"://'EPSG:':
                epsgSource = "EPSG:4532";
                break;
            case "45"://'EPSG:':
                epsgSource = "EPSG:4533";
                break;
        }
        return transFromByEpsg(x, y, epsgSource, "EPSG:4490");
    }

    /**
     * 坐标投影转换
     *
     * @param x
     * @param y
     * @param epsgSource
     * @param epsgTarget
     * @return
     * @throws Exception
     */
    public static double[] transFromByEpsg(double x, double y, String epsgSource, String epsgTarget) throws Exception {
        Coordinate sourceCoord = new Coordinate(y,x);
        Coordinate targetCoord = new Coordinate(0, 0);
        try {
            CoordinateReferenceSystem crsSource = CRS.decode(epsgSource);
            CoordinateReferenceSystem crsTarget = CRS.decode(epsgTarget);
            MathTransform transform = CRS.findMathTransform(crsSource, crsTarget);
            JTS.transform(sourceCoord, targetCoord, transform);
            double[] targetCoords = {targetCoord.x,targetCoord.y};
            return targetCoords;
        } catch (TransformException te) {
            throw new Exception("坐标系转换错误：TransformException:" + te.getMessage());
        } catch (FactoryException fe) {
            throw new Exception("坐标系转换错误：FactoryException:" + fe.getMessage());
        } catch (Exception e) {
            throw new Exception("坐标系转换错误：Exception:" + e.getMessage());
        }
    }



    /**
     * 创建点坐标
     * @param x 经度
     * @param y 纬度
     * @return
     */
    public static Point createPoint(Double x, Double y){
        //创建几何工厂
        GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
        //创建一个几何坐标
        Coordinate coordinate = new Coordinate(x, y);
        return geometryFactory.createPoint(coordinate);
    }

    /**
     * 将要素的坐标系转为相对应的坐标系
     * @param feature 要素类
     * @param targetSrid 坐标系id
     * @return
     */
    public static Geometry coordinateTransform(SimpleFeature feature, int targetSrid){
        if (feature!=null){
            return null;
        }
        try {
            CRSAuthorityFactory factory = CRS.getAuthorityFactory(true);
            CoordinateReferenceSystem target = factory.createCoordinateReferenceSystem("EPSG:" + targetSrid);
            CoordinateReferenceSystem coordinateReferenceSystem = feature.getDefaultGeometryProperty().getDescriptor().getCoordinateReferenceSystem();
            if (coordinateReferenceSystem == null){
                Geometry res = (Geometry)feature.getDefaultGeometry();
                res.setSRID(targetSrid);
                return res;
            }
            MathTransform transform = CRS.findMathTransform(coordinateReferenceSystem, target,true);
            Geometry res = JTS.transform((Geometry)feature.getDefaultGeometry(), transform);
            if (res != null){
                res.setSRID(targetSrid);
            }
            return res;
        }catch (FactoryException | TransformException e){
            e.printStackTrace();
        }
        return null;
    }



    /**
     * 根据解析类型创建点 (面解析方法中使用)
     *
     * @param pointStr     坐标点x y数组
     * @param analysisType 解析类型 1.十进制度、2.度分秒、3.投影
     * @return
     */
    public static Point polygonAnalysis_point(String[] pointStr, Integer analysisType) {
        //根据得到的类型解析每个点
        switch (analysisType) {
            case 1:
                return pointDecimalism(pointStr[0], pointStr[1]);
            case 2:
                return pointDFM(pointStr[0], pointStr[1]);
            case 3:
                return pointEPSG4490(pointStr[0], pointStr[1]);
        }
        return null;
    }


    /**
     * 检查输入的十进制度点坐标
     *
     * @param x 经度
     * @param y 纬度
     */
    public static Point pointDecimalism(String x, String y) {
        if (!(BasicUtil.isDouble(x, CoordinateUtil.LNG_PATTERN) && BasicUtil.isDouble(y, CoordinateUtil.LAT_PATTERN))) {
            return null;
        }
        return CoordinateUtil.createPoint(Double.parseDouble(x), Double.parseDouble(y));
    }

    /**
     * 检查输入的度分秒点坐标
     *
     * @param x 经度
     * @param y 纬度
     */
    public static Point pointDFM(String x, String y) {
        if (!(BasicUtil.isPattern(x, CoordinateUtil.LNG_PATTERN_D) && BasicUtil.isPattern(y, CoordinateUtil.LAT_PATTERN_D))){
            return null;
        }
        //转为十进制度
        double lon = Double.parseDouble(CoordinateUtil.Dms2D(x));
        double lat = Double.parseDouble(CoordinateUtil.Dms2D(y));
        return CoordinateUtil.createPoint(lon, lat);
    }

    /**
     * 检查输入的2000大地点坐标
     *
     * @param x 经度
     * @param y 纬度
     */
    public static Point pointEPSG4490(String x, String y) {
        try {
            double[] doubles = CoordinateUtil
                    .transTo4490fromProjectionByEpsg(Double.parseDouble(x), Double.parseDouble(y), y.substring(0, 2));
            return CoordinateUtil.createPoint(doubles[0], doubles[1]);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 读取WKT格式数据
     * @param WKT
     * @return
     */
    public static Geometry readWKT(String WKT){
        //读取坐标
        WKTReader2 wktReader = new WKTReader2();
        try {
            return wktReader.read(WKT);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}
